package com.xndc.netty.iec104.codec;

import com.xndc.netty.iec104.model.ASDU;
import com.xndc.netty.iec104.model.IEC104ControlFrame;
import com.xndc.netty.iec104.model.IEC104Frame;
import com.xndc.netty.iec104.model.IEC104InformationFrame;
import com.xndc.netty.iec104.model.InformationElement;
import com.xndc.netty.iec104.model.InformationObject;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;

/**
 * IEC 104编码器
 */
@Slf4j
public class Iec104Encoder extends MessageToByteEncoder<IEC104Frame> {
    
    private static final byte START_CHAR = 0x68;
    
    @Override
    protected void encode(ChannelHandlerContext ctx, IEC104Frame frame, ByteBuf out) throws Exception {
        // 将IEC104Frame对象编码为字节流
        byte[] frameBytes = frame.encode();
        
        // 如果帧对象自身的编码方法未实现，这里提供默认实现
        if (frameBytes.length == 0) {
            if (frame instanceof IEC104ControlFrame) {
                frameBytes = encodeControlFrame((IEC104ControlFrame) frame);
            } else if (frame instanceof IEC104InformationFrame) {
                frameBytes = encodeInformationFrame((IEC104InformationFrame) frame);
            }
        }
        
        // 写入到输出缓冲区
        if (frameBytes.length > 0) {
            out.writeBytes(frameBytes);
        }
    }
    
    /**
     * 编码控制帧(S帧或U帧)
     */
    private byte[] encodeControlFrame(IEC104ControlFrame frame) {
        // 控制帧的APDU通常是6字节(启动字符+长度+4字节控制域)
        byte[] apdu = new byte[6];
        
        // 设置启动字符和长度
        apdu[0] = START_CHAR;
        apdu[1] = 4;  // 控制域长度
        
        // 设置控制域
        if (frame.getControlType() == IEC104ControlFrame.ControlType.S_FRAME) {
            // S帧: 0000 0001
            apdu[2] = 0x01;
            apdu[3] = 0x00;
            
            // 设置接收序列号
            short receiveSeq = frame.getReceiveSequence();
            receiveSeq = (short)(receiveSeq << 1);
            apdu[4] = (byte)(receiveSeq & 0xFF);
            apdu[5] = (byte)((receiveSeq >> 8) & 0xFF);
        } else {
            // U帧: 0000 0011
            apdu[2] = 0x03;
            apdu[3] = 0x00;
            apdu[4] = 0x00;
            apdu[5] = 0x00;
            
            // 根据控制类型设置功能位
            switch (frame.getControlType()) {
                case STARTDT_ACT:
                    apdu[2] |= 0x04;  // 启动激活
                    break;
                case STARTDT_CON:
                    apdu[2] |= 0x08;  // 启动确认
                    break;
                case STOPDT_ACT:
                    apdu[2] |= 0x10;  // 停止激活
                    break;
                case STOPDT_CON:
                    apdu[2] |= 0x20;  // 停止确认
                    break;
                case TESTFR_ACT:
                    apdu[2] |= 0x40;  // 测试激活
                    break;
                case TESTFR_CON:
                    apdu[2] |= 0x80;  // 测试确认
                    break;
            }
        }
        
        return apdu;
    }
    
    /**
     * 编码信息帧(I帧)
     * 注意：这里提供的是简化实现，真实应用需要完整编码ASDU
     */
    private byte[] encodeInformationFrame(IEC104InformationFrame frame) {
        // 获取ASDU对象
        ASDU asdu = frame.getAsdu();
        if (asdu == null) {
            log.warn("尝试编码没有ASDU的信息帧");
            // 仅编码控制域部分
            byte[] apdu = new byte[6];
            
            // 设置启动字符和长度
            apdu[0] = START_CHAR;
            apdu[1] = 4;  // 控制域长度
            
            // 设置发送序列号
            short sendSeq = frame.getSendSequence();
            sendSeq = (short)(sendSeq << 1);  // 左移一位，最低位为0(I格式)
            apdu[2] = (byte)(sendSeq & 0xFF);
            apdu[3] = (byte)((sendSeq >> 8) & 0xFF);
            
            // 设置接收序列号
            short receiveSeq = frame.getReceiveSequence();
            receiveSeq = (short)(receiveSeq << 1);
            apdu[4] = (byte)(receiveSeq & 0xFF);
            apdu[5] = (byte)((receiveSeq >> 8) & 0xFF);
            
            return apdu;
        }
        
        // 计算ASDU长度 (简化实现，假设每个信息对象有1个元素)
        int asduLength = 4; // 类型标识(1) + VSQ(1) + COT(1) + 公共地址(1)
        
        // 添加信息对象的长度
        for (InformationObject io : asdu.getInformationObjects()) {
            // 每个对象: IOA(3) + 元素值(根据类型不同)
            asduLength += 3;
            
            for (InformationElement element : io.getElements()) {
                // 根据不同ASDU类型计算元素值长度
                switch(asdu.getTypeId()) {
                    case 1: // 单点信息
                        asduLength += 1;
                        break;
                    case 3: // 双点信息
                        asduLength += 1;
                        break;
                    case 5: // 步位置信息
                        asduLength += 1;
                        break;
                    case 7: // 32位比特串
                        asduLength += 4;
                        break;
                    case 9: // 归一化值
                    case 11: // 标度化值
                        asduLength += 2;
                        break;
                    case 13: // 短浮点数
                    case 36: // 短浮点数带时标
                    case 50: // 设点命令，归一化值
                    case 51: // 设点命令，标度化值
                    case 58: // 设点命令，短浮点数
                        asduLength += 4;
                        break;
                    case 45: // 单命令
                    case 46: // 双命令
                        asduLength += 1;
                        break;
                    case 100: // 总召唤命令
                    case 101: // 计数量召唤命令
                    case 105: // 复位进程命令
                        asduLength += 1;
                        break;
                    case 103: // 时钟同步命令
                        if (element.getTimestamp() != null) {
                            asduLength += element.getTimestamp().length;
                        } else {
                            asduLength += 7; // CP56Time2a 格式
                        }
                        break;
                    default:
                        asduLength += 1; // 默认假设为1字节
                }
            }
        }
        
        // 创建完整的APDU
        byte[] apdu = new byte[6 + asduLength];
        
        // 设置启动字符和长度
        apdu[0] = START_CHAR;
        apdu[1] = (byte)(4 + asduLength);  // 控制域长度(4) + ASDU长度
        
        // 设置发送序列号
        short sendSeq = frame.getSendSequence();
        sendSeq = (short)(sendSeq << 1);  // 左移一位，最低位为0(I格式)
        apdu[2] = (byte)(sendSeq & 0xFF);
        apdu[3] = (byte)((sendSeq >> 8) & 0xFF);
        
        // 设置接收序列号
        short receiveSeq = frame.getReceiveSequence();
        receiveSeq = (short)(receiveSeq << 1);
        apdu[4] = (byte)(receiveSeq & 0xFF);
        apdu[5] = (byte)((receiveSeq >> 8) & 0xFF);
        
        // 开始编码ASDU
        int offset = 6;
        
        // 类型标识
        apdu[offset++] = asdu.getTypeId();
        
        // VSQ
        apdu[offset++] = asdu.getVsq();
        
        // 传送原因
        byte cot = asdu.getCot();
        if (asdu.isTest()) {
            cot |= 0x80;
        }
        if (asdu.isNegative()) {
            cot |= 0x40;
        }
        apdu[offset++] = cot;
        
        // 公共地址
        apdu[offset++] = (byte)asdu.getCommonAddress();
        
        // 编码信息对象
        for (InformationObject io : asdu.getInformationObjects()) {
            // 信息对象地址(3字节，低字节在前)
            int ioa = io.getAddress();
            apdu[offset++] = (byte)(ioa & 0xFF);
            apdu[offset++] = (byte)((ioa >> 8) & 0xFF);
            apdu[offset++] = (byte)((ioa >> 16) & 0xFF);
            
            // 编码信息元素
            for (InformationElement element : io.getElements()) {
                Object value = element.getValue();
                
                // 根据不同ASDU类型编码元素值
                switch(asdu.getTypeId()) {
                    case 1: // 单点信息
                        if (value instanceof Boolean) {
                            apdu[offset++] = (byte)(((Boolean)value) ? 0x01 : 0x00);
                        } else {
                            apdu[offset++] = 0x00;
                        }
                        break;
                        
                    case 3: // 双点信息
                        if (value instanceof Number) {
                            apdu[offset++] = ((Number)value).byteValue();
                        } else {
                            apdu[offset++] = 0x00;
                        }
                        break;
                        
                    case 13: // 短浮点数
                        if (value instanceof Float) {
                            float floatValue = (Float)value;
                            int bits = Float.floatToIntBits(floatValue);
                            apdu[offset++] = (byte)(bits & 0xFF);
                            apdu[offset++] = (byte)((bits >> 8) & 0xFF);
                            apdu[offset++] = (byte)((bits >> 16) & 0xFF);
                            apdu[offset++] = (byte)((bits >> 24) & 0xFF);
                        } else {
                            // 默认为0.0
                            int bits = Float.floatToIntBits(0.0f);
                            apdu[offset++] = (byte)(bits & 0xFF);
                            apdu[offset++] = (byte)((bits >> 8) & 0xFF);
                            apdu[offset++] = (byte)((bits >> 16) & 0xFF);
                            apdu[offset++] = (byte)((bits >> 24) & 0xFF);
                        }
                        break;
                        
                    case 45: // 单命令
                        if (value instanceof Boolean) {
                            apdu[offset++] = (byte)(((Boolean)value) ? 0x01 : 0x00);
                        } else {
                            apdu[offset++] = 0x00;
                        }
                        break;
                        
                    case 100: // 总召唤命令
                        if (value instanceof Number) {
                            apdu[offset++] = ((Number)value).byteValue();
                        } else {
                            apdu[offset++] = 0x14; // 默认值20
                        }
                        break;
                        
                    case 103: // 时钟同步命令
                        byte[] timestamp = element.getTimestamp();
                        if (timestamp != null && timestamp.length > 0) {
                            System.arraycopy(timestamp, 0, apdu, offset, timestamp.length);
                            offset += timestamp.length;
                        } else {
                            // 如果没有时间戳，填充7个字节为0
                            for (int i = 0; i < 7; i++) {
                                apdu[offset++] = 0x00;
                            }
                        }
                        break;
                        
                    default:
                        // 默认处理
                        if (value instanceof Number) {
                            apdu[offset++] = ((Number)value).byteValue();
                        } else if (value instanceof Boolean) {
                            apdu[offset++] = (byte)(((Boolean)value) ? 0x01 : 0x00);
                        } else {
                            apdu[offset++] = 0x00;
                        }
                }
            }
        }
        
        log.debug("编码信息帧: 类型={}, 数据长度={}", asdu.getTypeId(), apdu.length);
        return apdu;
    }
} 